package com.googlecode.contests_repo.core.model;



import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.regex.Pattern;

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;

import com.googlecode.contests_repo.core.config.ConfigurationManager;

/**
 * Manages Resources in the file system. Holds business logic for Resources
 * operations.
 * 
 * @author Svetla Marinova, M23322
 */
public class ResourcesUtils {
	private static final String DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'";
	private static final String TIMEZONE_STRING = "Zulu";

	private static List<Resource> fetch(File homeDir) {
		List<Resource> result = new ArrayList<Resource>();
		File[] listFiles = homeDir.listFiles();
		for (File file : listFiles) {
			if (file.isDirectory() && !file.getName().startsWith("_")
					&& !file.getName().startsWith("souls")) {
				Resource resource = getJSON(file);
				result.add(resource);
			}
		}
		return result;
	}

	/**
	 * Fetch resource from directory
	 * 
	 * @param <T>
	 * @param homeDir
	 * @return list of resources
	 */
	@SuppressWarnings("unchecked")
	public static <T> List<T> fetchResources(File homeDir) {
		List<Resource> resources = fetch(homeDir);
		List<T> series = new ArrayList<T>(resources.size());
		for (Resource resource : resources) {
			series.add((T) resource);
		}
		return series;
	}

	/**
	 * returns the resource object associated with json for the resource
	 * contained in a given directory
	 * 
	 * @param <T>
	 * @param directory
	 * @return the resource object associated with json for the resource
	 *         contained in a given directory
	 */
	@SuppressWarnings("unchecked")
	public static <T> T getJSON(File directory) {
		File[] listFiles = directory.listFiles(new FileFilter() {

			public boolean accept(File pathname) {
				if (!pathname.isDirectory()
						&& !pathname.getName().startsWith(
								ConfigurationManager.getInstance()
										.getSecurityFileName())
						&& pathname.getName().endsWith(".json")) {
					return true;
				}
				return false;
			}
		});
		Resource resource = null;
		ObjectMapper mapper = new ObjectMapper();
		try {
			Class<? extends Resource> z = null;
			File resourceFile = listFiles[0];
			if (resourceFile.getName().startsWith("contest")) {
				z = Contest.class;
			} else if (resourceFile.getName().startsWith("problem")) {
				z = Problem.class;
			} else if (resourceFile.getName().startsWith("series")) {
				z = Series.class;
			}
			resource = mapper.readValue(resourceFile, z);

		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
		return (T) resource;
	}
    
	/**
	 * Gets the description files of the problem in the given directory.
	 * 
	 * @param problemDir
	 * 		The folder of the problem whose descriptions should be returned.
	 * @return
	 * 		The description files.
	 */
	public static File[] getProblemDescriptionFiles(File problemDir) {
		return listFilesByPrefix(problemDir, "description");
	}
	
	/**
	 * Gets the test files of the problem in the given directory.
	 * 
	 * @param problemDir
	 * 		The folder of the problem whose tests should be returned.
	 * @return
	 * 		The test files.
	 */
	public static File[] getProblemTestFiles(File problemDir) {
		return listFilesByPrefix(problemDir, "test");
	}
	
	/**
	 * Gets the solution files of the problem in the given directory.
	 * 
	 * @param problemDir
	 * 		The folder of the problem whose solutions should be returned.
	 * @return
	 * 		The solution files.
	 */
	public static File[] getProblemSolutionFiles(File problemDir) {
		return listFilesByPrefix(problemDir, "solution");
	}
	
	/**
	 * Gets the checker file of the problem in the given directory.
	 * 
	 * @param problemDir
	 * 		The folder of the problem whose checker should be returned.
	 * @return
	 * 		The checker file or null if there is no checker.
	 */
	public static File getProblemCheckerFile(File problemDir) {
		File[] files = listFilesByPrefix(problemDir, "checker");
		
		if (files.length == 0) {
			return null;
		}
		
		assert files.length == 1;
		return files[0];
	}
	
	private static File[] listFilesByPrefix(File parent, final String prefix) {
		return parent.listFiles(new FileFilter() {

			public boolean accept(File pathname) {
				if (pathname.isFile() && pathname.getName().startsWith(prefix)) {
					return true;
				}
				return false;
			}
		});
	}
	
	private static final String FILENAME_PATTERN = "[a-z0-9][a-z0-9_-]*";
	
	/**
	 * Pattern for the names of the resources.
	 * 
	 * Used for identifying which of the folders in a given contest for example,
	 * should contain problems. 
	 */
	public static final Pattern RESOURCE_NAME_PATTERN = Pattern.compile(FILENAME_PATTERN);

	/**
	 * Gets a list of all files containing children resources of the given one.
	 * For example, if the given folder contains a contest, this method will return
	 * all folders with problems.
	 * 
	 * @param folder
	 * 		The folder of the resource whose children should be returned.
	 * @return
	 * 		A list of files for all children resources of the given one.
	 */
	public static List<File> getResourceSubfolders(File folder) {
		List<File> resourceList = Arrays.asList(folder.listFiles(
				new FileFilter() {
					public boolean accept(File file) {
						String fileName = file.getName();
						return file.isDirectory()
								&& RESOURCE_NAME_PATTERN.matcher(fileName).matches();
					}
				}));
	
		return resourceList;
	}
	
	/**
	 * Checks whether the given contest has started.
	 * 
	 * @param contestDir
	 * 		The folder of the contest that should be checked.
	 * @return
	 * 		True if the contest has started, false otherwise.
	 */
	public static boolean hasContestStarted(File contestDir) {
		File contestJson = new File(contestDir, "contest.json");
		
		ObjectMapper mapper = new ObjectMapper();
		try {
			Contest contest = mapper.readValue(contestJson, Contest.class);
			return hasEnded(contest.getStartTime(), contest.getDuration());			
		} catch (JsonParseException e) {
			e.printStackTrace();
		} catch (JsonMappingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return false;
	}
	
	/**
	 * Checks whether the given contest has finished.
	 * 
	 * @param contestDir
	 * 		The folder of the contest that should be checked.
	 * @return
	 * 		True if the contest has finished, false otherwise.
	 */
	public static boolean hasContestFinished(File contestDir) {
		File contestJson = new File(contestDir, "contest.json");
		
		ObjectMapper mapper = new ObjectMapper();
		try {
			Contest contest = mapper.readValue(contestJson, Contest.class);
			return hasStarted(contest.getStartTime());		
		} catch (JsonParseException e) {
			e.printStackTrace();
		} catch (JsonMappingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return false;
	}
	
	/**
	 * Checks if a contest with the specified start date has already started.
	 * 
	 * @param dateString
	 * 		String representation of the start time.
	 * @return true if the resource has started, false otherwise
	 */
	public static boolean hasStarted(String dateString) {
		Date currentTime = new Date();
		if (currentTime.getTime() - parseDate(dateString).getTime() >= 0) {
			return true;
		}
		return false;
	}

	/**
	 * Checks if a contest with the specified start time and duration has already finished.
	 * 
	 * @param dateString
	 * 		String representation of the start time.
	 * @param duration
	 * 		Duration (in minutes) of the contest.
	 * @return true if the resource has ended, false otherwise
	 */
	public static boolean hasEnded(String dateString, int duration) {
		Date currentTime = new Date();
		DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG,
				DateFormat.LONG);
		dateFormat.setTimeZone(TimeZone.getTimeZone(TIMEZONE_STRING));
		if (currentTime.getTime()
				- (parseDate(dateString).getTime() + duration * 60) >= 0) {
			return true;
		}
		return false;
	}

	/**
	 * Parses the given date.
	 * 
	 * @param dateString
	 * 		The string representation of the date to parse.
	 * @return
	 * 		The Date representation of the given string.
	 */
	public static Date parseDate(String dateString) {
		if (dateString == null) {
			return null;
		}
	
		DateFormat dateFormat = new SimpleDateFormat(DATE_TIME_PATTERN);
		dateFormat.setTimeZone(TimeZone.getTimeZone(TIMEZONE_STRING));
		
		try {
			return dateFormat.parse(dateString);
		} catch (ParseException e) {
			return null;
		}
	}
}